package com.kgis.map.geometry.basetype {
   import com.kgis.map.common.util.GMath;
   import com.kgis.map.geometry.IGeometry;
   import com.kgis.map.geometry.PointGeometry;
   import com.kgis.map.geometry.PolygonGeometry;
   import com.kgis.map.geometry.PolylineGeometry;
   import com.kgis.map.projection.Projection;
   import com.kgis.map.projection.ProjectionPoint;
   import com.kgis.map.projection.ProjectionTransform;

   import mx.collections.ArrayCollection;
   import com.kgis.map.geometry.basetype.Location;
   import com.kgis.map.geometry.basetype.Size;

   /**
    * 范围 (xmin, ymin, xmax, ymax).
    */
   [Bindable]
   [RemoteClass(alias="com.kgis.map.model.support.Extent")]
   public class Extent {
      private var _xmin:Number;
      private var _ymin:Number;
      private var _xmax:Number;
      private var _ymax:Number;
      private var _proj:Projection;

      /**
       * Class constructor
       *
       * @param xmin xmin bound of Bounds instance
       * @param ymin ymin bound of Bounds instance
       * @param xmax xmax bound of Bounds instance
       * @param ymax ymax bound of Bounds instance
       */
      public function Extent(xmin:Number = NaN, ymin:Number = NaN, xmax:Number = NaN, ymax:Number = NaN, proj:Projection = null) {
         if (!isNaN(xmin)) {
            this.xmin = xmin;
         }
         if (!isNaN(ymin)) {
            this.ymin = ymin;
         }
         if (!isNaN(xmax)) {
            this.xmax = xmax;
         }
         if (!isNaN(ymax)) {
            this.ymax = ymax;
         }
         if (proj) {
            this._proj = proj;
         }
         else {
            this._proj = Projection.getProjProjection("EPSG:4326");
         }
      }

      public function get ymax():Number {
         return _ymax;
      }

      public function set ymax(value:Number):void {
         _ymax = value;
      }

      public function get xmax():Number {
         return _xmax;
      }

      public function set xmax(value:Number):void {
         _xmax = value;
      }

      public function get ymin():Number {
         return _ymin;
      }

      public function set ymin(value:Number):void {
         _ymin = value;
      }

      public function get xmin():Number {
         return _xmin;
      }

      public function set xmin(value:Number):void {
         _xmin = value;
      }

      public function clone():Extent {
         return new Extent(this._xmin, this._ymin, this._xmax, this._ymax, this._proj);
      }

      /**
       * Determines if the bounds passed as param is equal to current instance
       *
       * @param bounds Bounds to check equality
       * @return It is equal or not
       */
      public function equals(bounds:Extent):Boolean {
         var equals:Boolean = false;
         if (bounds != null) {
            equals = this.xmin == bounds.xmin && this.xmax == bounds.xmax && this.ymax == bounds.ymax && this.ymin == bounds.ymin;
         }
         return equals;
      }

      public function toString():String {
         return "xmin-ymin=(" + this.xmin + "," + this.ymin + ")" + " xmax-ymax=(" + this.xmax + "," + this.ymax + ")";
      }

      /**
       * Return a bbox string separating the bounds, with the decimal number passed as param, by commas.
       *
       * @param decimal Bounds number of decimals.
       * @return The bounds separated by commas.
       */
      public function boundsToString(decimal:Number = -1):String {
         if (decimal == -1) {
            decimal = 9;
         }
         var mult:Number = Math.pow(10, decimal);
         var bbox:String = Math.round(this.xmin * mult) / mult + "," + Math.round(this.ymin * mult) / mult + "," + Math.round(this.xmax * mult) / mult + "," + Math.round(this.ymax * mult) / mult;
         return bbox;
      }

      // Getters & setters for _xMin, _yMin, _xMax and _yMax

      public function get projection():Projection {
         return this._proj;
      }

      public function set projection(proj:Projection):void {
         this._proj = proj;
      }

      // Getters for width, height and size

      public function get width():Number {
         return Math.abs(this.xmax - this.xmin);
      }

      public function get height():Number {
         return Math.abs(this.ymax - this.ymin);
      }

      public function get size():Size {
         return new Size(this.width, this.height);
      }

      public function get center():Location {
         return new Location((this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2, this._proj);
      }

      public function add(x:Number, y:Number):Extent {
         return new Extent(this.xmin + x, this.ymin + y, this.xmax + x, this.ymax + y, this._proj);
      }

      /**
       * Extends the current instance of Bounds from a location.
       *
       * @param lonlat The LonLat which will extend the bounds.
       */
      public function extendFromLonLat(lonlat:Location):void {
         this.extendFromBounds(new Extent(lonlat.lon, lonlat.lat, lonlat.lon, lonlat.lat, this._proj));
      }

      /**
       * Extends the current instance of Bounds from bounds.
       *
       * @param bounds The bounds which will extend the current bounds.
       */
      public function extendFromBounds(bounds:Extent):void {
         this.xmin = (bounds.xmin < this.xmin) ? bounds.xmin : this.xmin;
         this.ymin = (bounds.ymin < this.ymin) ? bounds.ymin : this.ymin;
         this.xmax = (bounds.xmax > this.xmax) ? bounds.xmax : this.xmax;
         this.ymax = (bounds.ymax > this.ymax) ? bounds.ymax : this.ymax;
      }

      /**
       * Returns if the current bounds contains the LonLat passed as param
       *
       * @param loc The location to check
       * @param inclusive It will include the border's bounds ?
       * @return Lonlat is contained or not by the bounds
       */
      public function containsLocation(loc:Location, inclusive:Boolean = true,deltaWidth:Number=0,deltaHeight:Number=0):Boolean {
         var contains:Boolean = false;
         if (inclusive) {
            contains = ((loc.lon >= (this.xmin-deltaWidth)) && (loc.lon <= (this.xmax+deltaWidth)) && (loc.lat >= (this.ymin-deltaHeight)) && (loc.lat <= (this.ymax+deltaHeight)));
         }
         else {
            contains = ((loc.lon > (this.xmin-deltaWidth)) && (loc.lon < (this.xmax+deltaWidth)) && (loc.lat > (this.ymin-deltaHeight)) && (loc.lat < (this.ymax+deltaHeight)));
         }
         return contains;
      }

      /**
       * Determines if the bounds passed in param intersects the current bounds.
       *
       * @param bounds The bounds to test intersection.
       * @param inclusive It will include the border's bounds ?
       *
       * @return If the bounds intersects current bounds or not.
       */
      public function intersectsBounds(bounds:Extent, inclusive:Boolean = true):Boolean {
         var inyMin:Boolean = (bounds.ymin == this.ymin && bounds.ymax == this.ymax) ? true : (((bounds.ymin > this.ymin) && (bounds.ymin < this.ymax)) || ((this.ymin > bounds.ymin) && (this.ymin < bounds.ymax)));
         var inyMax:Boolean = (bounds.ymin == this.ymin && bounds.ymax == this.ymax) ? true : (((bounds.ymax > this.ymin) && (bounds.ymax < this.ymax)) || ((this.ymax > bounds.ymin) && (this.ymax < bounds.ymax)));
         var inxMax:Boolean = (bounds.xmax == this.xmax && bounds.xmin == this.xmin) ? true : (((bounds.xmax > this.xmin) && (bounds.xmax < this.xmax)) || ((this.xmax > bounds.xmin) && (this.xmax < bounds.xmax)));
         var inxMin:Boolean = (bounds.xmax == this.xmax && bounds.xmin == this.xmin) ? true : (((bounds.xmin > this.xmin) && (bounds.xmin < this.xmax)) || ((this.xmin > bounds.xmin) && (this.xmin < bounds.xmax)));

         return (this.containsBounds(bounds, true, inclusive) || bounds.containsBounds(this, true, inclusive) || ((inyMax || inyMin) && (inxMin || inxMax)));
      }

      /**
       * Returns if the current bounds contains the bounds passed as param
       *
       * @param bounds The bounds to check
       * @param partial Partial containing shoulds return true ?
       * @param inclusive It will include the border's bounds ?
       *
       * @return Bounds are contained or not by the bounds
       */
      public function containsBounds(bounds:Extent, partial:Boolean = false, inclusive:Boolean = true):Boolean {
         var inxMin:Boolean;
         var inyMax:Boolean;
         var inxMax:Boolean;
         var inyMin:Boolean;

         if (inclusive) {
            inxMin = (bounds.xmin >= this.xmin) && (bounds.xmin <= this.xmax);
            inyMax = (bounds.ymax >= this.ymin) && (bounds.ymax <= this.ymax);
            inxMax = (bounds.xmax >= this.xmin) && (bounds.xmax <= this.xmax);
            inyMin = (bounds.ymin >= this.ymin) && (bounds.ymin <= this.ymax);
         }
         else {
            inxMin = (bounds.xmin > this.xmin) && (bounds.xmin < this.xmax);
            inyMax = (bounds.ymax > this.ymin) && (bounds.ymax < this.ymax);
            inxMax = (bounds.xmax > this.xmin) && (bounds.xmax < this.xmax);
            inyMin = (bounds.ymin > this.ymin) && (bounds.ymin < this.ymax);
         }

         return (partial) ? (inyMax || inyMin) && (inxMin || inxMax) : (inyMax && inxMin && inyMin && inxMax);
      }

      /**
       * Determines in which quadrant is placed the lonlat in relation to the current bounds.
       *
       * @param lonlat The lonlat we want to know the quadrant
       *
       * @return A string describing the quadrant (e.g. "bl" for ymin-xmin, "tr" for ymax-xmax etc.)
       */
      public function determineQuadrant(lonlat:Location):String {
         var quadrant:String = "";
         var center:Location = this.center;

         /* quadrant += (lonlat.lat < center.lat) ? "b" : "t";
          quadrant += (lonlat.lon < center.lon) ? "l" : "r"; */

         if (lonlat.lat < center.lat) {
            quadrant += "b";
         }
         else {
            quadrant += "t";
         }

         if (lonlat.lon < center.lon) {
            quadrant += "l";
         }
         else {
            quadrant += "r";
         }

         return quadrant;
      }

      /**
       * Returns a bounds instance from a string following this format: "xmin,ymin,xmax,ymax".
       *
       * @param str The string from which we want create a bounds instance.
       * @param projection
       *
       * @return An instance of bounds.
       */
      public static function getBoundsFromString(str:String, proj:Projection):Extent {
         var bounds:Array = str.split(",");
         return Extent.getBoundsFromArray(bounds, proj);
      }

      /**
       * Returns a bounds instance from an array following this format: [xmin,ymin,xmax,ymax].
       *
       * @param bbox The array from which we want create a bounds instance.
       * @param projection
       *
       * @return An instance of bounds.
       */
      public static function getBoundsFromArray(bbox:Array, proj:Projection):Extent {
         return new Extent(Number(bbox[0]), Number(bbox[1]), Number(bbox[2]), Number(bbox[3]), proj);
      }

      /**
       * Returns a bounds instance from a size instance.
       *
       * @param size The size instance from which we want create a bounds instance.
       *
       * @return An instance of bounds.
       */
      public static function getBoundsFromSize(size:Size):Extent {
         return new Extent(0, size.h, size.w, 0);
      }

      /**
       * Returns the opposite quadrant compared to the quadrant where is placed
       * the lonlat in relation to the current bounds.
       *
       * @param quadrant The quadrant to opposite
       *
       * @return A string describing the opposite quadrant
       */
      public static function oppositeQuadrant(quadrant:String):String {
         var opp:String = "";

         opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
         opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';

         return opp;
      }

      /**
       * Returns a bounds string from an url containing the bbox param
       *
       * @param url The url from which we want extract the bounds.
       * @return A string describing the bounds contained by the url.
       */
      public static function getBBOXStringFromUrl(url:String):String {
         var startpos:int = url.indexOf("BBOX=") + 5;
         if (startpos < 5) {
            startpos = url.indexOf("bbox=") + 5;
         }
         var endpos:int = url.indexOf("%26", startpos);
         if (endpos < 0) {
            endpos = url.length;
         }
         var tempBbox:String = url.substring(startpos, endpos);
         var tempBboxArr:Array = tempBbox.split("%2C");
         return tempBboxArr[0] + "," + tempBboxArr[1] + " " + tempBboxArr[2] + "," + tempBboxArr[3];
      }

      /**
       * Returns a bounds string from an instance of bounds
       *
       * @param bounds The bounds from which we want a string
       * @return A string describing the bounds.
       */
      public static function getBBOXStringFromBounds(bounds:Extent):String {
         return bounds.xmin + "," + bounds.ymin + " " + bounds.xmax + "," + bounds.ymax;
      }

      /**
       * Method to convert the bounds from a projection system to an other.
       *
       * @param source The source projection
       * @param dest The destination projection
       */
      public function transform(source:Projection, dest:Projection):void {
         this.projection = dest;
         if (source.srsCode == dest.srsCode)
            return;
         var pLB:ProjectionPoint = new ProjectionPoint(this._xmin, this._ymin);
         var pRT:ProjectionPoint = new ProjectionPoint(this._xmax, this._ymax);
         ProjectionTransform.transform(source, dest, pLB);
         ProjectionTransform.transform(source, dest, pRT);
         this._xmin = pLB.x;
         this._ymin = pLB.y;
         this._xmax = pRT.x;
         this._ymax = pRT.y;
      }

      /**
       * Create a new polygon geometry based on this bounds.
       *
       * @return A new polygon with the coordinates of this bounds.
       */ /*public function toGeometry():Polygon {
         return new Polygon(new <Geometry>[
         new LinearRing(new <Number>[
         this.xmin, this.ymin,
         this.xmax, this.ymin,
         this.xmax, this.ymax,
         this.xmin, this.ymax])
         ]);
       }*/

      /**
       * 得到两个范围的最小外接框
       */
      public function encloseExtent(extent:Extent):void {
         ymax = Math.max(extent.ymax, ymax);
         ymin = Math.min(extent.ymin, ymin);
         xmax = Math.max(extent.xmax, xmax);
         xmin = Math.min(extent.xmin, xmin);
      }

      /**
       * 得到范围和点的最小外接框
       */
      public function enclose(location:Location):void {
         ymax = Math.max(location.y, ymax);
         ymin = Math.min(location.y, ymin);
         xmax = Math.max(location.x, xmax);
         xmin = Math.min(location.x, xmin);
      }

      /**
       * Create a new polygon geometry based on this bounds.
       *
       * @return A new polygon with the coordinates of this bounds.
       */
      public function toGeometry():IGeometry {

         var polygongeo:PolygonGeometry = new PolygonGeometry();
         var polylinegeo1:PolylineGeometry = new PolylineGeometry();
         var pointArray:ArrayCollection = new ArrayCollection();
         pointArray.addItem(new PointGeometry(this.xmin, this.ymin));
         pointArray.addItem(new PointGeometry(this.xmax, this.ymin));
         pointArray.addItem(new PointGeometry(this.xmax, this.ymax));
         pointArray.addItem(new PointGeometry(this.xmin, this.ymax));
         pointArray.addItem(new PointGeometry(this.xmin, this.ymin));
         polylinegeo1.pointList = pointArray;

         var lineArray:ArrayCollection = new ArrayCollection();
         lineArray.addItem(polylinegeo1);
         polygongeo.lineList = lineArray;

         return polygongeo;

      }

      /**
       * extent是否包含一个线段
       */
      public function containsLineSection(p1:PointGeometry, p2:PointGeometry,widthDelta:Number,heightDelta:Number):Boolean {
         if (p1.x <= (this.xmax+widthDelta) && p1.y <= (this.ymax+heightDelta) && p2.x <= (this.xmax+widthDelta) && p2.y <= (this.ymax+heightDelta) && p1.x >= (this.xmin-widthDelta) && p1.y >= (this.ymin-heightDelta) && p2.x >= (this.xmin-widthDelta) && p2.y >= (this.ymin-heightDelta)) {
            return true;
         }
         else {
            return false;
         }
      }

      /**
       * extent的边是否与线段相交
       */
      public function crossLineSection(p1:PointGeometry, p2:PointGeometry,widthDelta:Number,heightDelta:Number):Boolean {
         if (GMath.lineCross(this.xmin-widthDelta, this.ymin-heightDelta, this.xmax+widthDelta, this.ymin-heightDelta, p1.x, p1.y, p2.x, p2.y) || GMath.lineCross(this.xmax+widthDelta, this.ymin-heightDelta, this.xmax+widthDelta, this.ymax+heightDelta, p1.x, p1.y, p2.x, p2.y)
            || GMath.lineCross(this.xmax+widthDelta, this.ymax+heightDelta, this.xmin-widthDelta, this.ymax+heightDelta, p1.x, p1.y, p2.x, p2.y) || GMath.lineCross(this.xmin-widthDelta, this.ymax+heightDelta, this.xmin-widthDelta, this.ymin-heightDelta, p1.x, p1.y, p2.x, p2.y)) {
            return true;
         }
         else {
            return false;
         }
      }


   }
}

